using NLog;
using System.Data;
using System.Diagnostics;
using System.Text.Json;

namespace TPXSDK.NET.UObject;

/// <summary>
/// UAV Object对象管理器
/// </summary>
public class UObjAdmin
{
    #region 成员变量
    private readonly Logger logger = LogManager.GetLogger("UObjAdmin");
    /// <summary>
    /// UObject Meta签名
    /// </summary>
    public uint Signature { get; private set; }

    /// <summary>
    /// 设备类型
    /// </summary>
    public UDevType DevType { get; set; }

    /// <summary>
    /// UObject Meta 列表集合
    /// </summary>
    public List<UObjMeta> AllMeta { get; set; } = [];

    /// <summary>
    /// UAV Object 类型总个数
    /// </summary>
    public int TypeCnt => AllMeta?.Count ?? 0;

    /// <summary>
    /// UAV Object 对象列表
    /// </summary>
    public List<UObjBase> ObjList { get; set; } = [];

    #endregion

    #region 委托事件
    public delegate void ChangedHandler(UObjBase obj, byte memId);
    public event ChangedHandler? ChangedEvent;
    #endregion

    /// <summary>
    /// 构造函数，根据签名和元信息创建UObj对象集合
    /// </summary>
    public UObjAdmin(uint signature, UDevType devType, bool onlySetting = false)
    {
        DevType = devType;
        Signature = signature;

        var metaFn = Path.Combine(AppContext.BaseDirectory, $"UObjMeta\\{signature:X}-{devType}.json");
        if (!LoadMeta(metaFn))
        {
            AllMeta.Clear();
            ObjList.Clear();
            return;
        }
        InitUobjListByMeta(onlySetting);
    }

    private void InitUobjListByMeta(bool onlySetting = false)
    {
        foreach (var meta in AllMeta)
        {
            meta.TypeName = meta.TypeName.TrimEnd('\0');
            if (onlySetting)
            {
                if (!meta.IsSetting) continue;
            }

            for (var i = 0; i < meta.InstCnt; i++)
            {
                ObjList.Add(new UObjBase
                {
                    InstId = i,
                    Meta = meta
                });
            }
        }
    }

    /// <summary>
    /// 从模板文件创建UObjMeta对象列表
    /// </summary>
    private bool LoadMeta(string metaFn)
    {
        if (!File.Exists(metaFn))
        {
            return false;
        }

        using var fs = new FileStream(metaFn, FileMode.Open, FileAccess.Read);
        using var sr = new StreamReader(fs);
        var strSig = sr.ReadLine();
        var strDevType = sr.ReadLine();
        if (string.IsNullOrEmpty(strSig) || string.IsNullOrEmpty(strDevType))
        {
            return false;
        }

        try
        {
            Signature = uint.Parse(strSig, System.Globalization.NumberStyles.HexNumber);
            DevType = (UDevType)Enum.Parse(typeof(UDevType), strDevType);
            var meta = JsonSerializer.Deserialize<List<UObjMeta>>(sr.ReadToEnd());            
            if (meta == null) return false;

            AllMeta = meta;
            return AllMeta is { Count: > 0 };
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// 保存UObjMeta对象列表到模板文件
    /// </summary>
    public bool SaveMeta(string metaFn = "")
    {
        if (string.IsNullOrEmpty(metaFn))
        {
            metaFn = Path.Combine(AppContext.BaseDirectory, $"UObjMeta\\{Signature:X}-{DevType}.json");
        }

        try
        {
            // 检查文件命名规则有效性
            var dir = Path.GetDirectoryName(metaFn);
            if (string.IsNullOrEmpty(dir))
            {
                return false;
            }

            // 创建文件夹路径
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            // 保存Meta数据
            using var fs = new FileStream(metaFn, FileMode.Create, FileAccess.ReadWrite);
            using var sw = new StreamWriter(fs);
            sw.WriteLine($"{Signature:X}");
            sw.WriteLine($"{DevType}");
            sw.WriteLine(JsonSerializer.Serialize(AllMeta));
            
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// 通过名称索引获得和设置UObject对象
    /// </summary>
    public UObjBase? this[string name]
    {
        get
        {
            if (name.Contains('[') && name.Contains(']'))
            {
                string[] seg = name.Split("[");
                if (seg.Length == 2)
                {
                    // remove "[ ]"
                    var instId = Convert.ToInt32(seg[1][0..^1]);
                    return GetUObj(seg[0], instId);
                }

                Debug.Assert(false, $"UObj {name} Not find!");
                return null;
            }
            else
            {
                return GetUObj(name, 0);
            }
        }
    }

    /// <summary>
    /// 翻译类型编码为对应的字符串
    /// </summary>
    private bool GetTypeId(string strType, out byte typeId)
    {
        // 简化类型前缀
        if (strType.StartsWith("UObj"))
        {
            strType = strType[4..];
        }

        foreach (var meta in AllMeta.Where(meta => meta.TypeName == strType))
        {
            typeId = meta.TypeId;
            return true;
        }

        typeId = 0xFF;
        return false;
    }

    private string GetTypeName(byte typeId)
    {
        foreach (var meta in AllMeta.Where(meta => meta.TypeId == typeId))
        {
            return meta.TypeName;
        }
        return string.Empty;
    }

    #region FieldID编解码

    /// <summary>
    /// 检测FieldId是否为有效
    /// </summary>
    public bool IsValidFieldId(uint fieldId)
    {
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);
        if (typeId >= TypeCnt)
        {
            return false;
        }

        var obj = GetUObj(typeId, instId);
        if (null == obj)
        {
            return false;
        }

        if (memId > obj.Meta.MemCnt)
        {
            return false;
        }

        if (instId >= obj.Meta.InstCnt)
        {
            return false;
        }

        return true;
    }

    /// <summary>
    /// Field类型解码
    /// </summary>
    public static byte TypeId(uint fieldId) { return (byte)((fieldId & 0xFF000000) >> 24); }

    /// <summary>
    /// Field成员解码
    /// </summary>
    public static byte MemId(uint fieldId) { return (byte)((fieldId & 0x00FF0000) >> 16); }

    /// <summary>
    /// Field实例解码
    /// </summary>
    public static ushort InstId(uint fieldId) { return (ushort)(fieldId & 0x0000FFFF); }

    /// <summary>
    /// Field编码【三段法】
    /// </summary>
    public static uint FieldId(byte typeId, byte memId, ushort instId)
    {
        return (uint)((typeId & 0xFF) << 24 | (memId & 0xFF) << 16 | instId & 0xFFFF);
    }

    /// <summary>
    /// Field编码【对象法】
    /// </summary>
    public static uint FieldId(UObjBase obj)
    {
        return (uint)((obj.Meta.TypeId & 0xFF) << 24 | obj.InstId & 0xFFFF);
    }

    /// <summary>
    /// Field编码【对象法+特定成员】
    /// </summary>
    public static uint FieldId(UObjBase obj, byte memId)
    {
        var typeId = obj.Meta.TypeId;
        var instId = obj.InstId;
        return (uint)((typeId & 0xFF) << 24 | (memId & 0xFF) << 16 | instId & 0xFFFF);
    }

    /// <summary>
    /// 获得指定UAV Object对象和对应成员名称的FieldId
    /// </summary>
    public static uint FieldId(UObjBase obj, string memName)
    {
        var typeId = obj.Meta.TypeId;
        var instId = obj.InstId;
        var memId = Array.IndexOf(obj.Meta.MemName, memName) + 1;
        Debug.Assert(memId > 0);
        return (uint)((typeId & 0xFF) << 24 | (memId & 0xFF) << 16 | instId & 0xFFFF);
    }

    /// <summary>
    /// 解析字符串获得FieldId，解析失败抛异常
    /// </summary>
    public uint FieldId(string para)
    {
        var leftBracket = para.IndexOf('[');
        var rightBracket = para.IndexOf(']');
        var comma = para.IndexOf('.');
        // Not Match Brackets
        if (leftBracket * rightBracket < 0)
        {
            logger.Error($"Invalid FieldName:{para}");                
            return UObjBase.InvalidFieldId;
        }

        byte typeId;
        ushort instId;
        byte memId;

        string typeName;
        if (-1 == leftBracket && -1 == rightBracket)
        {
            if (-1 == comma)
            {//Format: 'UObjxxx'
                if (!GetTypeId(para, out typeId))
                {
                    return UObjBase.InvalidFieldId;
                }

                instId = 0;
                memId = 0;
                return FieldId(typeId, memId, instId);
            }

            //Format: 'UObjxxx.yyyy'
            typeName = para[..comma];
            if (!GetTypeId(typeName, out typeId))
            {
                logger.Error($"Invalid FieldName:{para}");
                return UObjBase.InvalidFieldId;
            }
            var memName = para[(comma + 1)..];
            var uobjs = GetUObjs(typeId);
            if (null == uobjs || uobjs.Count == 0)
            {
                logger.Error($"Invalid FieldName:{para}");
                return UObjBase.InvalidFieldId;
            }

            memId = (byte)(1 + Array.IndexOf(uobjs[0].Meta.MemName, memName));
            if (memId > 0)
            {
                instId = 0;
                return FieldId(typeId, memId, instId);
            }

            logger.Error($"Invalid FieldName:{para}");
            return UObjBase.InvalidFieldId;
        }

        var strInstId = para.Substring(leftBracket + 1, rightBracket - leftBracket - 1);
        if (!ushort.TryParse(strInstId, out instId))
        {
            logger.Error($"Invalid Field Name: {para}");
            return UObjBase.InvalidFieldId;
        }

        typeName = para[..leftBracket];
        if (!GetTypeId(typeName, out typeId))
        {
            logger.Error($"Invalid Field Name: {para}");
            return UObjBase.InvalidFieldId;
        }

        if (-1 == comma)
        {//Format: 'UObjxxx[i]'
            memId = 0x00;
        }
        else
        {//Format: 'UObjxxx[i].yyyy'
            var memName = para[(comma + 1)..];
            var uobjs = GetUObjs(typeId);
            if (null == uobjs || uobjs.Count == 0) return UObjBase.InvalidFieldId;
            var idx = Array.IndexOf(uobjs[0].Meta.MemName, memName);
            if (idx < 0)
            {
                logger.Error($"Invalid Field Name: {para}");
                return UObjBase.InvalidFieldId;
            }

            memId = (byte)(1 + idx);
        }

        return FieldId(typeId, memId, instId);
    }

    /// <summary>
    /// 翻译FieldId为对应的字符串
    /// </summary>
    public string FieldId2Str(uint fieldId)
    {
        var strUObj = string.Empty;
        if (!IsValidFieldId(fieldId)) return strUObj;

        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);
        var obj = GetUObj(typeId, instId);
        if (null == obj) return strUObj;

        var typeNm = GetTypeName(typeId);
        if (obj.Meta.InstCnt == 1)
        {
            strUObj = memId == 0x00 ? typeNm : $"{typeNm}.{obj.Meta.MemName[memId - 1]}";
        }
        else
        {
            strUObj = memId == 0x00 ? $"{typeNm}[{instId}]" : $"{typeNm}[{instId}].{obj.Meta.MemName[memId - 1]}";
        }

        // 简化UObj类前缀
        if (strUObj.StartsWith("UObj"))
        {
            strUObj = strUObj[4..];
        }

        return strUObj;
    }
    #endregion

    #region FieldID值读写
    /// <summary>
    /// 获得FieldId对应的Value所占字节大小
    /// </summary>
    public int FieldSize(uint fieldId)
    {
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);

        if (typeId >= TypeCnt)
        {
            return 0;
        }

        var obj = GetUObj(typeId, instId);
        if (null == obj) return 0;

        return 0 == memId ? obj.Meta.TypeSz :
                            obj.Meta.MemSize(memId - 1);
    }

    /// <summary>
    /// 设置FieldID对应的Value字节流
    /// </summary>
    public bool SetValue(uint fieldId, byte[] pData, int startIndex)
    {
        if (false == IsValidFieldId(fieldId)) return false;
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);

        var obj = GetUObj(typeId, instId);
        if (null == obj) return false;

        if (0 == memId)
        {
            if (obj.Update(pData, startIndex))
            {
                ChangedEvent?.Invoke(obj, memId);
                return true;
            }
        }
        else
        {
            var sz = obj.MemberUpdate(memId - 1, pData, startIndex);
            if (sz > 0)
            {
                ChangedEvent?.Invoke(obj, memId);
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 设置FieldID对应的Value对象
    /// </summary>
    public bool SetValue(uint fieldId, object val)
    {
        if (false == IsValidFieldId(fieldId)) return false;
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);

        var obj = GetUObj(typeId, instId);
        if (null == obj || 0 == memId)
        {
            return false;
        }

        if (obj.MemberUpdate(memId - 1, val) > 0)
        {
            ChangedEvent?.Invoke(obj, memId);
            return true;
        }

        return false;
    }

    /// <summary>
    /// 获取FieldID对应值并转换为字节流
    /// </summary>
    /// <param name="fieldId">域ID</param>
    /// <param name="pData">值缓存</param>
    /// <param name="startIndex">起始索引</param>
    /// <returns>值占用字节数</returns>
    public int GetValue(uint fieldId, byte[] pData, int startIndex)
    {
        if (false == IsValidFieldId(fieldId)) return 0;
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);
        var obj = GetUObj(typeId, instId);
        var bts = obj?.MemberToBytes(memId - 1);
        if (bts == null)
        {
            return 0;
        }

        foreach (var b in bts)
        {
            pData[startIndex++] = b;
        }

        return FieldSize(fieldId);
    }

    /// <summary>
    /// 获取FieldID对应的Value对象
    /// </summary>
    /// <param name="fieldId">域ID</param>
    /// <returns>值对象</returns>
    public object? GetValue(uint fieldId)
    {
        if (false == IsValidFieldId(fieldId)) return null;
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);

        var obj = GetUObj(typeId, instId);

        if (null == obj || 0 == memId) return null;

        obj.GetValue(memId, out object? res);
        return res;
    }

    /// <summary>
    /// 将特定fieldId的值序列化为字节流
    /// </summary>
    public byte[]? FieldVal2Bytes(uint fieldId, object val)
    {
        if (false == IsValidFieldId(fieldId)) return null;
        var typeId = TypeId(fieldId);
        var memId = MemId(fieldId);
        var instId = InstId(fieldId);
        var obj = GetUObj(typeId, instId);
        if (null == obj || memId == 0) return null;

        return obj.Meta.MemType[memId - 1] switch
        {
            MetaDataType.DT_U8 => [Convert.ToByte(val)],
            MetaDataType.DT_S8 => [(byte)Convert.ToSByte(val)],
            MetaDataType.DT_U16 => BitConverter.GetBytes(Convert.ToUInt16(val)),
            MetaDataType.DT_S16 => BitConverter.GetBytes(Convert.ToInt16(val)),
            MetaDataType.DT_U32 => BitConverter.GetBytes(Convert.ToUInt32(val)),
            MetaDataType.DT_S32 => BitConverter.GetBytes(Convert.ToInt32(val)),
            MetaDataType.DT_FP32 => BitConverter.GetBytes(Convert.ToSingle(val)),
            _ => null
        };
    }

    #endregion

    #region 对象获取
    /// <summary>
    /// 按类型编码获得对象集合
    /// </summary>
    public List<UObjBase> GetUObjs(int typeId)
    {
        return [.. ObjList.Where(obj => obj.Meta.TypeId == typeId)];
    }

    /// <summary>
    /// 按类型字符串获得对象集合
    /// </summary>
    public List<UObjBase> GetUObjs(string strType)
    {
        if (strType.StartsWith("UObj"))
        {//remove prefix "UObj"
            strType = strType[4..];
        }
        return [.. ObjList.Where(obj => obj.Meta.TypeName == strType)];
    }

    /// <summary>
    /// 按类型编码和实例号获得对象
    /// </summary>
    public UObjBase? GetUObj(int typeId, int instId)
    {
        return ObjList.FirstOrDefault(obj => obj.Meta.TypeId == typeId && obj.InstId == instId);
    }

    /// <summary>
    /// 根据FieldID获得对象
    /// </summary>
    public UObjBase? GetUObj(uint fieldId)
    {
        var typeId = TypeId(fieldId);
        var instId = InstId(fieldId);

        return ObjList.FirstOrDefault(obj => obj.Meta.TypeId == typeId && obj.InstId == instId);
    }

    /// <summary>
    /// 按类型字符串和实例号获得对象
    /// </summary>
    public UObjBase? GetUObj(string strType, int instId = 0)
    {
        if (strType.StartsWith("UObj"))
        {
            //remove prefix "UObj"
            strType = strType[4..];
        }
        return ObjList.FirstOrDefault(obj => obj.Meta.TypeName == strType && obj.InstId == instId);
    }
    #endregion

    #region 对象删改

    /// <summary>
    /// 更新UObj对象到当前列表,并通知变化,支持参数格式如下：
    /// XXX.yyy=ZZZ
    /// XXX[i].yyy=ZZZ
    /// </summary>
    public bool UpdateUObj(string para)
    {
        var segs = para.Split('=', StringSplitOptions.RemoveEmptyEntries);
        if (segs.Length != 2) return false;
        try
        {
            var fieldId = FieldId(segs[0]);
            SetValue(fieldId, segs[1]);
            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// 更新UObj对象到当前列表,并通知变化
    /// </summary>
    public bool UpdateUObj(UObjBase obj)
    {
        var oriObj = GetUObj(obj.Meta.TypeId, obj.InstId);
        if (oriObj == null) return false;

        if (ReferenceEquals(obj, oriObj))
        {
            ChangedEvent?.Invoke(oriObj, 0);
            return true;
        }

        if (oriObj.SetValue(obj))
        {
            ChangedEvent?.Invoke(oriObj, 0);
            return true;
        }

        return false;
    }

    /// <summary>
    /// 按类型字符串删除对象
    /// </summary>
    public void RemoveObj(string strType)
    {
        var uobjs = GetUObjs(strType);
        foreach (var obj in uobjs)
        {
            ObjList.Remove(obj);
        }
    }
    #endregion

    #region 参数操作

    /// <summary>
    /// 所有参数对象集合
    /// </summary>
    public List<UObjBase> Settings => [.. ObjList.Where(obj => obj.IsSetting)];

    /// <summary>
    /// 加载参数文件并创建新对象
    /// </summary>
    static public UObjAdmin? LoadSetting(string fileName)
    {
        if (!File.Exists(fileName))
        {
            // logger.Error($"Setting file{fileName} not exist");
            return null;
        }

        using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
        using var sr = new StreamReader(fs);

        var strSig = sr.ReadLine();
        if (string.IsNullOrEmpty(strSig))
        {
            return null;
        }

        var strDevType = sr.ReadLine();
        if (string.IsNullOrEmpty(strDevType))
        {
            return null;
        }

        if (!Enum.TryParse(strDevType, out UDevType devType))
        {
            return null;
        }

        var sig = uint.Parse(strSig, System.Globalization.NumberStyles.HexNumber);
        var admin = new UObjAdmin(sig, devType, true);
        if (admin.AllMeta.Count == 0)
        {
            return null;
        }

        while (!sr.EndOfStream)
        {
            var line = sr.ReadLine();
            if (string.IsNullOrEmpty(line)) continue;

            // 删除整行注释
            if (line.StartsWith(@"//") || line.StartsWith("#"))
            {
                continue;
            }
            // 删除行尾注释
            var index = line.IndexOf("//", StringComparison.Ordinal);
            if (-1 != index) line = line[..index];

            if (string.IsNullOrEmpty(line))
            {
                continue;
            }

            if (!admin.UpdateUObj(line))
            {
                throw new Exception($"Match Error: {line}");
            }
        }

        return admin;
    }

    /// <summary>
    /// 保存参数对象到文件
    /// </summary>
    public bool SaveSetting(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
        {
            return false;
        }

        using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
        using var sw = new StreamWriter(fs);
        sw.WriteLine($"{Signature:X}");
        sw.WriteLine(DevType);
        foreach (var obj in Settings)
        {
            sw.Write(obj.DumpToStr());
        }
        return true;
    }

    #endregion

    #region DataTable转换

    private static string MetaDataType2Str(MetaDataType dt)
    {
        return dt switch
        {
            MetaDataType.DT_S8 => "S8",
            MetaDataType.DT_U8 => "U8",
            MetaDataType.DT_S16 => "S16",
            MetaDataType.DT_U16 => "U16",
            MetaDataType.DT_S32 => "S32",
            MetaDataType.DT_U32 => "U32",
            MetaDataType.DT_FP32 => "FP32",
            _ => "DT?"
        };
    }

    /// <summary>
    /// 将UObjAdmin管理的UObject对象转换为DataTable
    /// </summary>
    public DataTable GetDataTable(bool onlySetting = false)
    {
        var table = new DataTable("UObjAdmin");
        var dc = new DataColumn[5];

        dc[0] = new DataColumn("FieldName") { ReadOnly = true };
        dc[1] = new DataColumn("FieldVal") { ReadOnly = false };
        dc[2] = new DataColumn("DataType") { ReadOnly = true };
        dc[3] = new DataColumn("Status") { ReadOnly = false };
        dc[4] = new DataColumn("Unit") { ReadOnly = true };
        table.Columns.AddRange(dc);

        foreach (var obj in ObjList)
        {
            if (onlySetting && !obj.Meta.IsSetting)
            {
                continue;// 过滤非参数对象
            }
            var prefixNm = obj.Meta.InstCnt == 1 ? $"{obj.Meta.TypeName}" : $"{obj.Meta.TypeName}[{obj.InstId}]";
            for (var i = 0; i < obj.Meta.MemName.Length; i++)
            {
                obj.GetValue((byte)(i + 1), out object? val);

                var row = table.NewRow();
                row[0] = $"{prefixNm}.{obj.Meta.MemName[i]}";
                row[1] = val ?? DBNull.Value;
                row[2] = MetaDataType2Str(obj.Meta.MemType[i]);
                row[3] = string.Empty;
                row[4] = $"{obj.Meta.MemUnit[i]}";
                table.Rows.Add(row);
            }
        }

        return table;
    }
    #endregion
}
